package com.cooper.resource.download;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.StreamCorruptedException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import com.cooper.android.comm.FileUtils;
import com.cooper.scraper.DownloadException;
import com.cooper.scraper.DownloadParams;
import com.cooper.scraper.HttpDownload;

import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.WifiManager;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.util.Base64;
import android.util.Log;

public abstract class DownloadManagerService extends Service implements
		DownloadListener {

	public final static String PREF_NAME = "DOWNLOADING";

	public class DownloadBinder extends Binder implements DownloadControl {

		public void setDownloadListener(DownloadListener listener) {
			downloadlistener = listener;
		}

		public DownloadStatus addDownload(DownloadResource resource) {
			DownloadStatus result = addDownloadResource(resource);
			result.saveDownInfo();
			return result;
		}
		
		public boolean downloadPrefIsExists(File storeFile){
			SharedPreferences pref = getSharedPreferences(PREF_NAME,
					Context.MODE_PRIVATE);
			String strObj = pref.getString(storeFile.getAbsolutePath(), "");
			return (strObj != null) && !("".equals(strObj)); 
		}

		@Override
		public Collection<DownloadStatus> getDownloading() {
			List<DownloadStatus> result = new ArrayList<DownloadStatus>();
			result.addAll(downloading.keySet());
			result.addAll(finishDown);
			Collections.sort(result, DownloadComparator.getInstance());
			return result;
		}

		@Override
		public void pause(DownloadStatus res) {
			Future<?> task = downloading.get(res);
			if (task != null && !task.isCancelled() && !task.isDone()) {
				task.cancel(true);
			}
		}

		@Override
		public void downContinue(DownloadStatus res) {
			continueItem(res);
		}

		private void remove(DownloadStatus res) {
			File tempFile = res.getDownloadResource().getDownloadingSaveDir();
			SharedPreferences pref = getSharedPreferences(PREF_NAME,
					Context.MODE_PRIVATE);
			Editor editor = pref.edit();
			editor.remove(tempFile.getAbsolutePath());
			editor.commit();
			FileUtils.delete(tempFile);
		}

		@Override
		public void cancel(DownloadStatus res) {
			pause(res);
			if (finishDown.contains(res)) {
				finishDown.remove(res);
			}
			downloading.remove(res);
			remove(res);
		}

		@Override
		public boolean completeMove(DownloadStatus res) {
			return moveComplete(res);
		}

		@Override
		public void pauseAll() {
			pauseAllItem();
		}

		@Override
		public void continueAll() {
			continueAllItem();
		}

		@Override
		public void cancelAll() {
			pauseAll();
			for (DownloadStatus status : downloading.keySet()) {
				remove(status);
			}
			finishDown.clear();
			downloading.clear();
		}

	}

	private DownloadListener downloadlistener;

	private ExecutorService threadPool;

	private DownloadBinder downloadBinder = new DownloadBinder();

	private Map<DownloadStatus, Future<?>> downloading = new HashMap<DownloadStatus, Future<?>>();

	private List<DownloadStatus> finishDown = new ArrayList<DownloadStatus>();

	// TODO config DownloadParams
	private HttpDownload httpDownload = new HttpDownload(new DownloadParams());

	// private List<DownloadStatus> paush

	private DownloadStatus addDownloadResource(DownloadResource resource) {
		DownloadStatus result = new DownloadStatus(this, resource, this);
		addDownloadResource(result);
		return result;
	}

	private void addDownloadResource(DownloadStatus res) {
		downThumb(res);
		if (res.getDownloadResource().isFinish()) {
			finishDown.add(res);
		} else {
			downloading.put(res, threadPool.submit(new DownloadResourceThread(
					httpDownload, res, getTaskThreadCount())));
		}

	}

	@Override
	public IBinder onBind(Intent intent) {
		return downloadBinder;
	}

	private boolean pauseByWifi = false;

	private BroadcastReceiver receiver = new BroadcastReceiver() {

		private void checkWifiConnect(NetworkInfo networkInfo) {
			if ((NetworkInfo.State.CONNECTED == networkInfo.getState())
					&& (networkInfo.getType() == ConnectivityManager.TYPE_WIFI)) {
				if (pauseByWifi) {
					continueAllItem();
					pauseByWifi = false;
				}
			}else if (!pauseByWifi){
				pauseAllItem();
				pauseByWifi = true;
			}
		}

		@Override
		public void onReceive(Context context, Intent intent) {
			if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(intent
					.getAction())) {

				NetworkInfo networkInfo = intent
						.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
				if (null != networkInfo)
					checkWifiConnect(networkInfo);
			}

			if (WifiManager.WIFI_STATE_CHANGED_ACTION
					.equals(intent.getAction())) {
				int wifiState = intent.getIntExtra(
						WifiManager.EXTRA_WIFI_STATE,
						WifiManager.WIFI_STATE_UNKNOWN);
				switch (wifiState) {
				case WifiManager.WIFI_STATE_DISABLED:
				case WifiManager.WIFI_STATE_DISABLING:
					Log.d("DownloadManagerService", "wifi deconnect");
					pauseAllItem();
					pauseByWifi = true;
					break;
				}
			}

			if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent
					.getAction())) {
				NetworkInfo networkInfo = intent
						.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
				if (networkInfo != null) {
					checkWifiConnect(networkInfo);
				}
			}
		}
	};

	@Override
	public void onCreate() {

		Log.d("DownloadService", "start onCreate~~~");
		super.onCreate();
		// Runtime.getRuntime().availableProcessors() * THREAD_RUN_COUNT
		threadPool = Executors.newFixedThreadPool(getMaxTaskCount());
		IntentFilter filter = new IntentFilter();
		filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
		filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
		filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
		registerReceiver(receiver, filter);

		init();
	}

	protected abstract int getMaxTaskCount();

	protected abstract int getTaskThreadCount();

	protected abstract List<String> getMonitorDirs();

	protected abstract boolean moveFile(DownloadResource res);

	private boolean moveComplete(DownloadStatus res) {
		if (!res.getDownloadResource().isFinish())
			return false;
		boolean result = moveFile(res.getDownloadResource());
		if (result) {
			Editor editor = getSharedPreferences(PREF_NAME,
					Context.MODE_PRIVATE).edit();
			editor.remove(res.getDownloadResource().getDownloadingSaveDir()
					.getAbsolutePath());
			editor.commit();
			finishDown.remove(res);
		}
		return result;
	}

	private void pauseAllItem() {
		for (Future<?> task : downloading.values()) {
			if (task != null && !task.isCancelled() && !task.isDone()) {
				task.cancel(true);
			}
		}
	}

	private void continueAllItem() {
		for (DownloadStatus status : downloading.keySet()) {
			continueItem(status);
		}
	}

	private void continueItem(DownloadStatus res) {
		Future<?> task = downloading.get(res);
		if (task == null || task.isCancelled() || task.isDone()) {
			addDownloadResource(res);
		}
	}

	@Override
	public void onStart(Intent intent, int startId) {
		Log.d("DownloadService", "start onStart~~~");
		super.onStart(intent, startId);
	}

	private void init() {
		SharedPreferences pref = getSharedPreferences(PREF_NAME,
				Context.MODE_PRIVATE);

		for (String monitorPath : getMonitorDirs()) {
			File monitorDir = new File(monitorPath);
			if (!monitorDir.exists()) {
				monitorDir.mkdirs();
			}
			List<DownloadStatus> recoveryDownloads = new ArrayList<DownloadStatus>();
			//if sdcard mount fail throw exception
			for (File file : monitorDir.listFiles()) {
				boolean recoverDown = false;
				if (file.isDirectory()) {
					String strObj = pref.getString(file.getAbsolutePath(), "");
					Log.d("DownloadManagerService",
							"load obj form:" + file.getAbsolutePath());
					if (strObj != null && !strObj.equals("")) {
						try {
							recoveryDownloads.add(recoveryDown(strObj));
							recoverDown = true;
						} catch (Exception e) {
							Log.e("DownloadManagerService", "recoveryDownFail",
									e);
						}

					}
				}
				if (!recoverDown) {
					FileUtils.delete(file);
				}
			}
			Collections.sort(recoveryDownloads,
					DownloadComparator.getInstance());

			NetworkInfo networkInfo = ((ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE))
					.getActiveNetworkInfo();
			if (!recoveryDownloads.isEmpty()) {
				if (networkInfo == null) {
					pauseByWifi = true;
				} else
					pauseByWifi = !(NetworkInfo.State.CONNECTED == networkInfo
							.getState())
							&& (networkInfo.getType() == ConnectivityManager.TYPE_WIFI);
				for (DownloadStatus res : recoveryDownloads) {
					if (pauseByWifi) {
						if (res.getDownloadResource().isFinish()) {
							finishDown.add(res);
						} else {
							downloading.put(res, null);
							res.downComplete();
						}
					} else
						addDownloadResource(res);
				}
			}
		}

		List<String> deleteKeys = new ArrayList<String>();
		for (String path : pref.getAll().keySet()) {
			if (!new File(path).exists()) {
				deleteKeys.add(path);
			}
		}
		if (!deleteKeys.isEmpty()) {
			Editor editor = pref.edit();
			for (String path : deleteKeys) {
				editor.remove(path);
			}
			editor.commit();
		}
	}

	private DownloadStatus recoveryDown(String infoFile)
			throws StreamCorruptedException, IOException,
			ClassNotFoundException {

		ByteArrayInputStream bais = new ByteArrayInputStream(Base64.decode(
				infoFile, Base64.DEFAULT));
		try {
			ObjectInputStream ois = new ObjectInputStream(bais);
			try {
				return new DownloadStatus(this,
						(DownloadResource) ois.readObject(), this);

			} finally {
				ois.close();
			}
		} finally {
			bais.close();
		}

	}

	@Override
	public void onDestroy() {
		Log.d("DownloadService", "start onDestroy~~~");
		for (Future<?> task : downloading.values()) {
			task.cancel(true);
		}
		unregisterReceiver(receiver);
		super.onDestroy();
	}

	@Override
	public boolean onUnbind(Intent intent) {
		Log.d("DownloadService", "start onUnbind~~~");
		downloadlistener = null;
		return super.onUnbind(intent);
	}

	@Override
	public void onBegin(DownloadStatus res) {
		if (downloadlistener != null)
			downloadlistener.onBegin(res);
	}

	@Override
	public void onComplete(DownloadStatus res) {
		downloading.remove(res);
		finishDown.add(res);
		if (downloadlistener != null)
			downloadlistener.onComplete(res);

	}

	@Override
	public void onUpdate(DownloadStatus res) {
		if (downloadlistener != null)
			downloadlistener.onUpdate(res);

	}

	@Override
	public void onFail(DownloadStatus res) {
		Future<?> task = downloading.get(res);
		if (task != null) {
			task.cancel(true);
		}

		if (downloadlistener != null) {
			downloadlistener.onFail(res);
		}
	}

	@Override
	public void onThumbComplete(DownloadStatus res) {
	}

	private Handler ThumbHandle = new Handler();

	private void downThumb(final DownloadStatus res) {
		ThumbHandle.post(new Runnable() {

			@Override
			public void run() {
				try {
					File fileSaveDir = res.getDownloadResource()
							.getDownloadingSaveDir();
					if (!new File(fileSaveDir.getAbsolutePath() + "/"
							+ DownloadStatus.THUMB_FILE_NAME).exists()) {
						if (!fileSaveDir.exists())
							if (!fileSaveDir.mkdirs()) {
								return;
							}

						File destFile = new File(fileSaveDir.getAbsolutePath()
								+ "/thumb.down");
						httpDownload.downloadFile(res.getDownloadResource()
								.getThumbUrl(), destFile);
						destFile.renameTo(new File(fileSaveDir
								.getAbsolutePath()
								+ "/"
								+ DownloadStatus.THUMB_FILE_NAME));
						if (downloadlistener != null) {
							downloadlistener.onThumbComplete(res);
						}
					}
				} catch (DownloadException e) {
					Log.e("DownloadManagerService", e.getMessage(), e);
				}

			}

		});
	}

}
